package algebra.cost;

import utils.exception.CostEstimateException;

import entities.concept.Concept;
import kb.DescriptionLogicsKB;
import algebra.op.AlgebraicOP;
import algebra.op.ConstantOP;
import algebra.op.JoinOP;
import algebra.op.PrimaryOP;
import algebra.op.ProjectionOP;
import algebra.op.ScanOP;
import algebra.op.SelectionOP;
import algebra.query.indexing.DescriptionIndex;
import algebra.rewriting.kfree.KFreeRewriting;
import algebra.visitors.cursors.CACursorDI;
import algebra.visitors.cursors.CACursorDI.CursorType;
import algebra.visitors.cursors.CACursorJoin;
import algebra.visitors.cursors.CACursorJoin.JoinAlgorithm;

public class PlanEstimation {

	// private final DescriptionLogicsKB kb;
	private final SizeEstimation se;

	private final static double costOfConstant = 1.0e-8; // for constant
															// queries
	private final static double costOfKB = 10; // for each request
	private final static double costOfNullKB = 1.0e-4; // for each request
	private final static double costOfPIKB = costOfConstant * 3; // array op
	private final static double costOfTraversingSI = 1.0e-5; 
	private final static double costOfSearchingSI = 10 * costOfTraversingSI; 
	private final static double costOfProjKB = costOfKB + costOfNullKB;
	private final static double costOfProjNoKB = costOfNullKB * 3;
	private final static double costOfIntersection = costOfConstant * 5; // fixed
	
	private final static double portionOfNodesToSearch = 0.5;

	public PlanEstimation(DescriptionLogicsKB kb) {
		// this.kb = kb;
		this.se = new SizeEstimation(kb);
	}

	
	//the total cost of an op is: the cost of fetching one answer * estimated size of the subop
	public double getPlanCost(AlgebraicOP op) throws CostEstimateException {
		double one = fetchOneCost(op);
		double size = 1;
		if (op instanceof ConstantOP) {
//			return one;
			size = se.sizeofOP(op);
		} else if (op instanceof JoinOP) {
			size = se.sizeofOP(op.asIntersectionOP().getFirstSubQuery());
		} else if (op instanceof PrimaryOP) {
			size = se.sizeofOP(op);
		} else if (op instanceof ProjectionOP) {
			size = se.sizeofOP(op);
		} else if (op instanceof ScanOP) {
			size = se.sizeofOP(op.asScanOP().getSubquery());
		} else if (op instanceof SelectionOP) {
			size = se.sizeofOP(op);
		} else {
			throw new CostEstimateException("Unable to compute total cost for OP: "
					+ op);
		}
		return one * size;
	}
	private double fetchOneCost(AlgebraicOP op) throws CostEstimateException {
		if (op instanceof ConstantOP) {
			return costOfConstant();
		} else if (op instanceof JoinOP) {
			return costOfIntersection(op.asIntersectionOP());
		} else if (op instanceof PrimaryOP) {
			return costOfPrimary(op.asPrimaryOP());
		} else if (op instanceof ProjectionOP) {
			return costOfProjection(op.asProjectionOP());
		} else if (op instanceof ScanOP) {
			return this.costOfScanQ(op.asScanOP());
		} else if (op instanceof SelectionOP) {
			return this.costOfSelection(op.asSelectionOP());
		} else {
			throw new CostEstimateException("Unable to compute te cost of fetching a tuple from "
					+ op);
		}
	}

	private double costOfPrimary(PrimaryOP pp) {
		return costOfPIKB;
	}

	private double joinStopPortion = 1; 
	//return at least this portin of CAs to find a matching one
	private double costOfIntersection(JoinOP op) throws CostEstimateException {
		AlgebraicOP left = op.getFirstSubQuery(), right=op.getSecondSubQuery();
		boolean lstar = (left instanceof ConstantOP), rstar = (right instanceof ConstantOP);
		if(lstar && rstar){
			return 1*costOfIntersection;
		}else if(lstar){
			return this.fetchOneCost(right)+costOfIntersection;
		}else if(rstar){
			return this.fetchOneCost(left)+costOfIntersection;
		}
		//determine the join algorithm
		JoinAlgorithm ja = CACursorJoin.chooseJoin(left, right);
		double cost = 0;
		switch(ja){
			case NESTED_LOOP: 
				//right is inner, left is outer, 
				//cost is costofLeft + (sizeof left * costof right)
				cost = this.fetchOneCost(right) * joinStopPortion * se.sizeofOP(right);
				cost += this.fetchOneCost(left) + costOfIntersection;
				return cost;
			case MERGE_JOIN: 
				cost = this.fetchOneCost(left) + this.fetchOneCost(right) + costOfIntersection;
				return cost;
			default: return cost;
		}
	}

	private double costOfConstant() {
		return costOfConstant;
	}

	private double costOfProjection(ProjectionOP pj) {
		double cost = 0.;
		double kbcost = 0.0;
		if (pj.getPlanEvalKB() != null) {
			kbcost += costOfProjKB;
		} else {
			kbcost += costOfProjNoKB; // we need reductions as well
		}
		try {
			cost += se.sizeOfPd(pj.getPrjDesc(), KFreeRewriting
					.getCapturedConstants().size())
					* kbcost  + this.fetchOneCost(pj.getSubquery());
		} catch (CostEstimateException e) {
			e.printStackTrace();
		}
		return cost;
	}

	// //the cost of a Pd largely depends on the size of Lpd, base cost is 1
	// private static double PdCpComp = 0.5;
	// private static double PdFComp = 0.2;
	// private static double PdRComp = 0.8;
	// private static double PdConjComp = 1;
	// private double costOfPd(ProjectionDescription pd, double subsize, boolean
	// hasKB){
	// KBStat stat = kb.getKBStat();
	// double comp = 1.0;
	// switch(pd.getType()){
	// case ProjectionDescription.CONCEPT:
	// if(pd.getConcept().equals(KBVocabulary.CONCEPT_TOP)){
	// //easier KB reasoning
	// if(hasKB){
	// comp = 1 * costOfPIKB/costOfProjKB ;
	// }else{
	// comp = 0.5;
	// }
	//
	// }else{
	// comp += PdCpComp;
	// }
	//
	// break;
	// case ProjectionDescription.FEATURE:
	// Role f = Role.createRole(pd.getArg());
	// comp += PdFComp * stat.getFeatureValueSizeInKB(f) *
	// (subsize/stat.getTotalInstances());
	// break;
	// case ProjectionDescription.ROLE:
	// Role R = Role.createRole(pd.getArg());
	// double possibleSuccessors = this.kb.getKBStat().getRoleSizeInKB(R);
	// ProjectionDescription sub = pd.getPd1();
	// comp += PdRComp * subsize + costOfPd(sub, possibleSuccessors, hasKB);
	// break;
	// case ProjectionDescription.CONJUNCTION:
	// comp += PdConjComp * (costOfPd(pd.getPd1(), subsize, hasKB) +
	// costOfPd(pd.getPd2(), subsize, hasKB));
	// break;
	// }
	// return comp;
	// }

	private double costOfSelection(SelectionOP sp) {
		double cost = 0.0;
		AlgebraicOP sub = sp.getSubquery();
		if (sp.getPlanEvalKB() != null) {
			cost += costOfKB;
		} else {
			cost += costOfNullKB;
		}
		try {
			cost += this.fetchOneCost(sub);
		} catch (CostEstimateException e) {
			e.printStackTrace();
		}
		return cost;
	}

	
	//depending on the way to access the index
	private double costOfScanQ(ScanOP sp) {
		double cost = 0.0;
		Concept sc = null;
		if (sp.getSubquery() instanceof ConstantOP) {
			sc = sp.getSubquery().asConstantOP().getQueryCocnept();
		}
		DescriptionIndex di = sp.getSecondaryIndex().getDescriptionIndex();
		Concept ci = sp.getSecondaryIndex().getCi();
		boolean star = sc!=null? true: false;
		CursorType type = CACursorDI.cursorType(star, sc, di);
		boolean reasoning = CACursorDI.reasoningRequired(star, sc, ci);
		double numOfSearch = 1, costOfDI = 0, costOfReason;
		costOfReason = reasoning? costOfNullKB:0;
//		if(star){
//			numOfSearch = 1;
//		}else{
//			numOfSearch = se.sizeofOP(sp.getSubquery());
//		}
		if(type.equals(CursorType.TRA)){
			costOfDI = (costOfTraversingSI+costOfReason);
			long nodesToSearch = 0;
			if(!reasoning){
				//then a portion of all nodes have to be searched to find one answer
				nodesToSearch = Math.round(1.0 /se.conceptSelectivity(sc));
			}else{
				//only one node is returned: every node is an answer
				nodesToSearch = 1;
			}
			costOfDI *= nodesToSearch;
		}else if(type.equals(CursorType.SORT)){
			double nodesToSearch = (Math.log(di.getSize()) / Math.log(2));
			//how many tops or equal order concepts? i.e., the chance to search more branches
//			times += di.getSize() * (di.getNumTops()+ di.getNumOfFeatureValues());
//			times += (di.getNumTops()+ di.getSize()/di.getNumOfFeatureValues());
			nodesToSearch *= di.getNumTops() + (di.getSize() * se.conceptSelectivity(sc)) * portionOfNodesToSearch;
//			costOfDI = di.getSize() *  times* (costOfReason + costOfSearchingSI);
			costOfDI = nodesToSearch* (costOfReason + costOfSearchingSI);
		}
		cost = costOfDI * numOfSearch;
		return cost;
	}

//	private double costOfScanC(ScanOP sp) {
//		double cost = 0.0;
//		Concept cond = sp.getSubquery().asConstantOP().getQueryCocnept();
//		SecondaryIndex si = sp.getSecondaryIndex();
//		OrderDep od = si.getOdi();
//		// initial cost: assume empty KB reasoning for every CA in Si
//		double numtest = se.sizeofOP(sp);
//		double dg = searchDegree(cond, od);
//		if (dg >= 0.99) {
//			// log_2(x) = log(x)/log(2)
//			numtest = Math.log(numtest) / Math.log(2);
//		} else {
//			// only a part of the concept can be searched, i.e., the whole SI
//			// will still be searched!
//			// numtest = numtest - (dg * Math.log(numtest)/Math.log(2));
//		}
//		cost += numtest * costOfSearchCAinSI;
//		return cost;
//	}

	// save the number of tests by log
//	private double searchDegree(Concept cond, OrderDep od) {
//		double deg = 0;
//		if (cond instanceof DomainConcept && od.majorSortOnArg(cond)) {
//			deg = 1;
//		} else {
//			Set<Concept> conj = Concept.reduceToConjuncts(cond);
//			// test all conjuncts of c
//			for (Concept c : conj) {
//				if (c instanceof DomainConcept && od.majorSortOnArg(c)) {
//					deg += 1.0 / conj.size();
//				}
//			}
//		}
//		return deg;
//	}

}
